/**
 * @author Vitor Franco do Carmo (vitor.carmo@cpmbraxis.com)
 * @since 31/07/2013
 *
 */
package br.com.vfcfw.business.service;

import java.util.List;

import javax.annotation.Resource;
import javax.ejb.EJBContext;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

import br.com.vfcfw.commons.domain.entity.BaseEntity;
import br.com.vfcfw.commons.domain.filter.BaseFilter;
import br.com.vfcfw.commons.exception.BusinessException;
import br.com.vfcfw.commons.util.ConstantesArquitetura;
import br.com.vfcfw.commons.util.ReflexaoUtil;
import br.com.vfcfw.persistence.exception.PersistenceException;

/**
 * Abstração com a implementação padrão para as classes de negócio do tipo CRUD.
 * 
 * @author Vitor Franco do Carmo (vitor.carmo@cpmbraxis.com)
 * @since 31/07/2013
 * 
 */
public abstract class BaseCRUDService<E extends BaseEntity> extends BaseService<E> {

    /** */
    private static final long serialVersionUID = -6088096137387665576L;

    @Resource
    private EJBContext context;

    /**
     * Construtor da classe.
     */
    public BaseCRUDService() {

        super();
    }

    /**
     * {@inheritDoc}
     * 
     * @see br.jus.tjce.arquitetura.business.base.BaseService#salvar(br.jus.tjce.arquitetura.domain.base.BaseEntity)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public E salvar(E entity) throws BusinessException {

        E entityIncluido = null;

        if (possuiValor(entity)) {

            if (!possuiValor(entity.getChavePrimaria())) {

                entityIncluido = incluir(entity);
            } else {

                entityIncluido = alterar(entity);
            }
        }
        return entityIncluido;
    }

    /**
     * {@inheritDoc}
     * 
     * @see br.jus.tjce.arquitetura.business.base.BaseService#incluir(br.jus.tjce.arquitetura.domain.base.BaseEntity)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public E incluir(E entity) throws BusinessException {

        E entityIncluido = null;

        validarCampos(entity);
        validarRegrasNegocioIncluir(entity);
        processarValidacoes();

        try {

            entityIncluido = getEntityDAO().incluir(entity);

        } catch (Exception e) {
            tratarException(e);
        }
        return entityIncluido;
    }

    /**
     * Persiste uma lista da entidade persistente.
     * 
     * @param listEntity {@link List} Lista da entidade persistente.
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void incluir(List<E> listEntity) {

        if (possuiValor(listEntity)) {

            for (E entity : listEntity) {

                validarCampos(entity);
                validarRegrasNegocioIncluir(entity);
                processarValidacoes();
            }

            try {

                getEntityDAO().incluir(listEntity);

            } catch (Exception e) {
                tratarException(e);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see br.jus.tjce.arquitetura.business.base.BaseService#alterar(br.jus.tjce.arquitetura.domain.base.BaseEntity)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public E alterar(E entity) throws BusinessException {

        E entityAlterado = null;

        validarPk(entity);
        validarCampos(entity);
        validarRegrasNegocioAlterar(entity);
        processarValidacoes();

        try {

            entityAlterado = getEntityDAO().alterar(entity);
        } catch (Exception e) {
            tratarException(e);
        }
        return entityAlterado;
    }

    /**
     * {@inheritDoc}
     * 
     * @see br.jus.tjce.arquitetura.business.base.BaseService#excluir(br.jus.tjce.arquitetura.domain.base.BaseEntity)
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public void excluir(E entity) throws BusinessException {

        validarPk(entity);
        validarRegrasNegocioExcluir(entity);
        processarValidacoes();

        try {

            getEntityDAO().excluir(entity);
        } catch (PersistenceException e) {
            tratarException(e);
        }
    }

    /**
     * Exclui uma lista de registros de uma entidade.
     * 
     * @param listEntity {@link List} de registros da entidade mapeada com JPA.
     */
    public void excluir(List<E> listEntity) {

        if (possuiValor(listEntity)) {

            for (E entity : listEntity) {

                validarPk(entity);
                validarRegrasNegocioExcluir(entity);
                processarValidacoes();
            }
        }

        try {

            getEntityDAO().excluir(listEntity);
        } catch (PersistenceException e) {
            tratarException(e);
        }
    }

    /**
     * Verifica se o registro esta sendo utilizado por outro registro.
     * 
     * @param entity Entidade mapeada com JPA.
     * @return <tt>TRUE</tt> está sendo utilizado, <tt>FALSE</tt> caso contrário.
     */
    public boolean validarRegistroUtilizado(E entity) {

        return !testarExcluir(entity);
    }

    /**
     * Testa se o registro pode ser excluído.
     * 
     * @param entity Entidade mapeada com JPA.
     * @return <tt>TRUE</tt> se pode ser excluído, <tt>FALSE</tt> caso contrário.
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    protected boolean testarExcluir(E entity) {

        boolean podeExcluir = true;

        try {

            excluir(entity);
            podeExcluir = true;

            rollback();

        } catch (Exception e) {
            podeExcluir = false;
        }
        return podeExcluir;
    }

    /**
     * Realiza o Rollback da transação ativa.
     */
    protected void rollback() {

        this.context.setRollbackOnly();
    }

    /**
     * Valida a unicidade de nome do {@link Objetivo}.
     * 
     * @param entity Entidade mapeada com JPA.
     * @param filtro Filtro da entidade mapeada com JPA.
     * @param nomeAtributo Nome do atributo que será validado.
     * @param inserindo <tt>TRUE</tt> se estiver inserindo um novo registro, <tt>FALSE</tt> caso contrário.
     */
    protected void validarUnicidade(E entity, BaseFilter<E> filtro, String nomeAtributo, boolean inserindo) {

        if (possuiValor(entity)) {

            Object valor;
            String nomeAtributoEntity = nomeAtributo;

            try {

                if (nomeAtributo.startsWith("entity")) {

                    nomeAtributoEntity = nomeAtributo.substring(nomeAtributo.indexOf(".") + 1);
                }
                valor = ReflexaoUtil.executarMetodoGet(entityClass, nomeAtributoEntity, entity);
                ReflexaoUtil.executarMetodoSet(filtro.getClass(), nomeAtributo, filtro, valor);
            } catch (Exception e) {
                adicionarErro(ConstantesArquitetura.MSG_ERRO_OBJETO_NULO, entityClass.getSimpleName());
            }

            List<E> lista = consultarPorCriterio(filtro);

            if (inserindo) {

                if (!lista.isEmpty()) {

                    adicionarErro(ConstantesArquitetura.MSG_ERRO_VALIDACAO_DUPLICACAO_ENTIDADE,
                        obterNomeCampo(nomeAtributoEntity));
                }
            } else {

                if (!lista.isEmpty()) {

                    E entityRetornado = lista.get(0);

                    if (!entityRetornado.equals(entity)) {

                        adicionarErro(ConstantesArquitetura.MSG_ERRO_VALIDACAO_DUPLICACAO_ENTIDADE,
                            obterNomeCampo(nomeAtributoEntity));
                    }
                }
            }

        } else {

            adicionarErro(ConstantesArquitetura.MSG_ERRO_OBJETO_NULO, entityClass.getSimpleName());
        }
    }

    /**
     * Valida as regras de negócio necessárias para persistir uma entidade.
     * 
     * @param entity Entidade mapeada com JPA.
     * @throws BusinessException
     */
    protected void validarRegrasNegocioIncluir(E entity) throws BusinessException {

    }

    /**
     * Valida as regras de negócio necessárias para alterar uma entidade.
     * 
     * @param entity Entidade mapeada com JPA.
     * @throws BusinessException
     */
    protected void validarRegrasNegocioAlterar(E entity) throws BusinessException {

    }

    /**
     * Valida as regras de negócio necessárias para excluir uma entidade.
     * 
     * @param entity Entidade mapeada com JPA.
     * @throws BusinessException
     */
    protected void validarRegrasNegocioExcluir(E entity) throws BusinessException {

    }

}
